Skip to content

Feat/library#136

Merged
MaryWylde merged 19 commits into
devfrom
feat/library
Jun 8, 2026
Merged

Feat/library#136
MaryWylde merged 19 commits into
devfrom
feat/library

Conversation

@MaryWylde
Copy link
Copy Markdown
Contributor

No description provided.

MaryWylde and others added 16 commits June 1, 2026 13:36
Import the keepsimple Library feature source from
github.com/keepsimpleio/library into keepsimple's existing folder
structure, namespaced under `library/` to avoid collisions:

- components atoms/molecules/organisms -> src/components/library/
- templates                            -> src/layouts/library/
- types (+ d.ts type shims)            -> src/local-types/library/
- utils                                -> src/utils/library/
- hooks                                -> src/hooks/library/
- constants (+ config/seo.config.ts)   -> src/constants/library/
- contexts                             -> src/components/Context/library/
- axios/cookie libs                    -> src/lib/library/
- client services (src/api + app/api)  -> src/api/library/
- code-imported assets                 -> src/assets/library/
- shared scss                          -> src/styles/library/
- AGENT.md -> src/components/library/LIBRARY_AGENT.md

Excluded per task: package.json/lockfile, node_modules, CI/workflows,
.claude/.husky/.idea, eslint/tsconfig, README/docs, Storybook (.storybook
+ *.stories.*), App-Router routing/infra (app/page, app/layout, loading,
app/auth), all NextAuth setup, and route handlers (libraries proxy,
geoip /api/user, test-login mock). Imports/deps/auth/route are fixed in
later steps.
Copy Library's public/images into public/library/images so they don't
collide with keepsimple's public root, and rewrite the three absolute
public references in the imported code from /images/... to
/library/images/...:

- atoms/Icon/Icon.tsx       — SVG sprite href (/library/images/icons/all.svg)
- constants/library/seo.config.ts — default OG image path
- layouts/library/Home/Home.tsx   — landing cover image

JS module imports of assets (@/assets/images/*) are alias rewrites,
handled in Step 4.
Add only the runtime packages the migrated Library code imports that
keepsimple was missing (versions from Library, since these are new —
no shared-version conflict). Shared packages (react, react-dom, next,
next-auth, classnames, react-tooltip, date-fns, sass, geoip-lite) keep
keepsimple's existing versions.

Added (dependencies):
- axios ^1.13.4            — Library Strapi client (src/lib/library/axios)
- js-cookie ^3.0.5         — cookie helper (src/lib/library/cookie)
- zod ^4.3.6               — form/validation schemas (src/utils/library/schema)
- react-hook-form ^7.71.1  — forms in modals
- @hookform/resolvers ^5.2.2 — zod<->react-hook-form bridge
- react-dropzone ^15.0.0   — ImageDropzone molecule
- react-day-picker ^10.0.0 — DatePicker molecule
- @dnd-kit/core ^6.3.1, @dnd-kit/sortable ^10.0.0, @dnd-kit/utilities ^3.2.2
                           — ReorderGrid drag-and-drop

Added (devDependencies):
- @types/js-cookie ^3.0.6  — js-cookie ships no types

Not added: commander/fs-extra (Library generators — not migrated),
Storybook/vitest/playwright tooling (excluded). Single yarn install run
with yarn classic (v1) to keep yarn.lock in its canonical v1 format.
Rewrite Library's @/* aliases to keepsimple's per-folder aliases, relocate
assets under @iCons, strip 'use client', convert next/navigation to
next/router, and rewire auth to keepsimple's NextAuth + @api/auth. Make
the next-auth.d.ts a proper module augmentation so it no longer clobbers
next-auth's default export.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add Pages Router entry points for the migrated Library feature:
/library (Home) and /library/[username] (dashboard). Both use
getServerSideProps for SSR and reuse keepsimple's SeoGenerator
instead of next/head blocks. Scope a sassOptions.additionalData
injection in next.config.js to library SCSS modules only, so the
placeholder selectors the original app injected globally resolve
without touching keepsimple's own styles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add the Library nav item (flagged via isLibraryEnabled), a "My Library"
shortcut in the user-profile dropdown, and notFound guards on the library
pages when the feature is disabled.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch the sticky global header to #F8F1E5 on /library paths (including the
dark-theme variant, since the library has no dark mode) so it blends with the
library surface.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Retire the old library Header organism in favor of a sticky LibraryToolbar:
shelf jump-pills with overflow arrows, a Search Everywhere input, and an Add
shelf control that disables at the 21-shelf cap. Adds Input onKeyDown for the
search and MAX_SHELVES_PER_LIBRARY.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move the library's global CSS behind a single library-global entry imported
from _app, scope resets under .library, and wrap portaled menus/modals in a
.library container so they inherit the tokens. Self-host Source Serif 4 (the
Google Fonts @import is blocked by CSP) and declare font-family vars.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Override SVGO's removeViewBox in the SVGR loader so downscaled shelf icons keep
their coordinate system instead of cropping, and strip stray blue frame-outline
artifacts from the book and video glyphs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Edit a shelf name by clicking it (drop the settings-menu edit entry), drop the
doubled brown border on the settings-trigger dropdown, append new shelves at the
end with an inline loader instead of a full-screen swap, hide private shelves
from non-owners, give overflowing object rows a 12px #C0B6AE scrollbar with 40px
top headroom, and glide cards to their new slots after a reorder via a FLIP
animation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add the reorderObjects/reorderShelves endpoints and wire the step-2 ReorderGrid
to persist on save. Resolve the shelf id from a passed-in fallback so edit mode
no longer silently skips, and sort populated shelves/objects by order:asc so the
saved sequence survives a refetch. Stop swallowing reorder errors — log status +
body, roll back the optimistic order, and message the user. Also derive the
ImageDropzone preview synchronously so the cover no longer flashes empty.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prefer the optimistically updated currentShelves over the stale one-shot
library fetch for the sidebar book/video/song totals so counts bump on upload,
and load the tag list on mount so the Tags panel isn't empty until the first
mutation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Always render the tag column (even when empty) and fix container dimensions so
book/audio/video cards keep a consistent width with or without tags, and paint
the book cover gradient immediately so freshly uploaded covers don't flash blank.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stamp publishedAt when creating a tag (the content type is draftAndPublish), so
newly created tags don't vanish on the next load.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@MaryWylde
Copy link
Copy Markdown
Contributor Author

@claude

@claude
Copy link
Copy Markdown

claude Bot commented Jun 3, 2026

Claude finished @MaryWylde's task in 2m 37s —— View job


PR Review: Feat/library

  • Gather PR context
  • Review API layer (src/api/library/)
  • Review component architecture (atoms, molecules, contexts)
  • Review config/infrastructure changes (next.config.js, package.json)
  • Review modifications to existing components (Header, Navbar, UserProfile)
  • Post comprehensive review feedback

Good foundational work — the atomic component hierarchy, Zod+RHF form validation, FLIP animation on card reorder, and the optimistic-with-rollback pattern in AddObjectModal are all well-done. Below are the issues worth resolving before merge.


Bugs

1. AuthContext — logout doesn't reset in-memory state

handleLogout clears the cookie but never calls setToken(null) or setAccountData(null). Any component reading token or accountData from context will continue to see the logged-in user until a hard refresh. Fix this →

2. GlobalStateContext — double fetch of libraries on first authenticated load

Two useEffects both call refetchLibraries() on mount when a token exists. The unconditional first effect ([refetchLibraries]) fires and doesn't set didAttemptLibrariesLoad.current, so the second effect's guard doesn't fire, and it calls refetchLibraries() a second time. Fix this →

3. AddObjectModal — "Select tag to reorder" dropdown is dead UI

The tag-filter dropdown in step 2 renders with no onChange or value, so it neither reads nor writes any state. It does nothing.

// src/components/library/organisms/AddObjectModal/AddObjectModal.tsx
<Dropdown
  options={selectedTags.map(t => ({ value: String(t.id), label: t.name }))}
  === 0 ? 'Pick tags above first' : 'Select a tag to filter'}
  disabled={selectedTags.length === 0}
  // ← no onChange or value
/>

Either wire it up to filter reorderItems or remove it until the feature is ready. Fix this →


Type Safety

4. GlobalStateContextlibraries typed as unknown

libraries: unknown | null;  // consumers must cast every time

getLibrariesList() returns StrapiLibrariesResponse | null when called from this context. Narrowing the type here eliminates downstream casts. Fix this →

5. createTag.ts — missing return type

createTag has no explicit return type annotation, unlike every other API function in this module. This leaves its resolved shape as any. Fix this →


Architecture / Consistency

6. getTagsList — App Router SSR branch in a Pages Router project

getTagsList has a typeof window === 'undefined' guard that dynamically imports next/headers (an App Router API). This project uses Pages Router (useRouter from next/router). In Pages Router SSR (getServerSideProps), next/headers is not available and this branch would throw. The comment says "when called from a Server Component" — but Pages Router has no Server Components.

If this is intentional preparation for an App Router migration, add a clear comment. If not, remove the dead branch.

7. reorderObjects / reorderShelves — no error handling

All other API functions in this module wrap their calls in try/catch and return null on failure. reorderObjects, reorderShelves, createObject, and uploadFile don't, so callers must handle every error path themselves. AddObjectModal does wrap the call in try/catch, but the inconsistency is a footgun for future callers. Fix this →

8. AuthContext — manual cookie manipulation instead of js-cookie

js-cookie is already a declared dependency. handleProviderSignIn and handleLogout manipulate document.cookie directly — including constructing Secure and SameSite attributes by hand. This is fragile and inconsistent with the getCookie helper used in useEffect. Use Cookies.remove() from js-cookie instead.


Minor / Cosmetic

9. UserProfile.tsxLibraryIcon has an unusual aspect ratio

The icon is rendered at width={20} height={11}, which is not square and doesn't match the other menu icons in the dropdown (Settings uses Image with no explicit non-square dimensions). Confirm this is intentional for the SVG's natural aspect ratio.

10. Incomplete hy locale in UserProfile

The hy (Armenian) locale label for myLibrary is 'My Library' (English), while ru already has the correct translation. This was likely an oversight. Fix this →

11. Three open TODOs blocking completeness

// TODO: hook up URL metadata auto-fetch (YouTube / Spotify / generic OG)
// TODO: drop the client-side `slug` once Strapi makes `object.slug` optional
// TODO: drop once Strapi sets `owner` in `beforeCreate`

All are in AddObjectModal.tsx. The slug and owner hacks are server-side dependencies — fine to ship with comments. The URL metadata fetch is a UI gap (the "Search" button does nothing); consider removing the button until it's wired up, so users don't click a no-op control.


MaryWylde and others added 2 commits June 3, 2026 19:07
- flatten src/api/library/library/ up one level and split strapi.ts into
  per-call files (getLibrariesList, getLibrariesPaginated, getSingleLibrary,
  user/getUserInfo, shared libraryCardPopulate); drop dead getShelves
- remove App-Router leftovers: molecules/SEO, molecules/SignInModal,
  molecules/UserDropDown, utils/library/seo.ts (host reuses SeoGenerator,
  login modal, and keepsimple header)
- consolidate library fonts into globals.scss; delete styles/library/fonts.scss
- Dropdown: move inline styles into module.scss classes
- AudioCard: replace hardcoded focus color with --focus-ring var
- AuthContext: reset in-memory token/account on logout, use js-cookie helpers
- GlobalStateContext: drop unconditional first fetch (double-load), type
  libraries as StrapiLibrariesResponse; simplify Sidebar narrowing
- createTag: explicit response type; getTagsList: drop dead SSR header branch
- UserProfile: fill in hy (Armenian) menu labels
- gitignore LIBRARY_TODO.md scratch file

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- gate /library/:username create UI + right panel on the can-create-library
  flag from /api/users/me
- warn instead of crashing when a shelf, object, or tag name already exists
- bundles in-progress object-select, shelf-rename, and share-URL work
  already in the tree

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@MaryWylde
Copy link
Copy Markdown
Contributor Author

@claude, review this again.

@claude
Copy link
Copy Markdown

claude Bot commented Jun 8, 2026

Claude finished @MaryWylde's task in 3m 26s —— View job


PR Review: Feat/library

  • Gather PR context
  • Review contexts (AuthContext, GlobalStateContext, DashboardContext)
  • Review API layer
  • Review component architecture (atoms, molecules, organisms)
  • Review modified files (Header, Navbar, UserProfile)
  • Check if prior review issues were addressed
  • Post comprehensive review

Good progress since the last pass — most of the previous issues have been addressed. Notes below on what's still open, what's resolved, and three new bugs surfaced in this read.


Prior Issues: Resolved ✅

# Issue Status
1 AuthContext — logout didn't reset token/accountData FixedsetToken(null) and setAccountData(null) now called in handleLogout
2 GlobalStateContext — double fetch of libraries on mount Fixed — only the token-gated effect remains
4 GlobalStateContextlibraries typed as unknown Fixed — now StrapiLibrariesResponse | null
5 createTag.ts — missing return type Fixed — now Promise<CreateTagResponse>
6 getTagsList — App Router SSR branch Fixed — dead branch removed
8 AuthContext — raw document.cookie manipulation Fixed — uses removeCookie from @lib/library/cookie
10 UserProfilehy locale for myLibrary was English Fixed — all three locales now have correct translations

Still Open From Previous Review

Bug: AddObjectModal — "Select tag to reorder" dropdown is dead UI

The Dropdown at AddObjectModal.tsx:751–763 still has no onChange or value. It renders but does nothing. Remove it until the feature is wired up, or add the filter logic. Fix this →

Minor: UserProfile.tsxLibraryIcon at non-square dimensions

LibraryIcon is rendered at width={20} height={11} (UserProfile.tsx:153). All other icons in the dropdown are square. Confirm this is intentional for the SVG's natural proportions, or correct to width={16} height={16}.

Minor: AddObjectModal — "Search" button is a no-op

The "Search" button in the sourceUrl field still calls an empty click handler (AddObjectModal.tsx:618–621). Users clicking it will get no feedback. Remove the button until the URL metadata fetch is implemented. Fix this →


New Issues

Bug 1: Sidebar.tsx — library dropdown navigates by numeric ID, not username

The dropdown options are built with lib.id (numeric Strapi ID) as the value:

// Sidebar.tsx:137–140
const dropdownOptions = libraryCards.map(lib => ({
  value: String(lib.id),  // ← numeric id
  label: lib.libraryName,
}));

handleLibraryChange then navigates to /library/${libraryId}, producing /library/123 instead of /library/johndoe. The URL route is [username], so this lands on a 404 or the wrong page. LibraryCard.tsx:27 correctly uses username ?? idSidebar should do the same.

Additionally, isViewingSelectedLibrary compares selectedLibrary.username === currentLibraryId (where currentLibraryId is the slug from the URL), but selectedLibraryId state holds a numeric ID — so this comparison is always false. Fix this →

Bug 2: Sidebar.tsx — tag slugs collide on duplicate names

Both handleCreateTag and handleEditTag generate slugs as ${formData.name.toLowerCase()}-1 (hardcoded -1 suffix). If the user creates two tags with different names that normalize to the same string (or renames a tag to match an existing one), both end up with identical slugs. Strapi enforces unique slugs, so the second write fails silently — the error is caught and re-thrown, but the UI shows no feedback from the catch in handleCreateTag. Use a timestamp suffix (like Date.now()) to guarantee uniqueness. Fix this →

Security: ObjectOverviewModal.tsxdangerouslySetInnerHTML on user-supplied description

// ObjectOverviewModal.tsx:500–502
<div
  className={styles.description}
  dangerouslySetInnerHTML={{ __html: attributes.description }}
/>

description is user-provided content stored in Strapi. If Strapi does not sanitize HTML on write (CKEditor data is rich text and typically is not stripped), this is a stored XSS vector. Add client-side sanitization with DOMPurify.sanitize() before passing to __html. Fix this →


Architecture Note

LibraryToolbar — search input is wired but does nothing

LibraryToolbar.tsx:104–105 has a comment // Search is UI-only for now — no query wiring yet and the search state setSearch is never consumed downstream. The input renders and the user can type in it, but there's no filter applied. Similar to the "Search" button in AddObjectModal — either wire it or remove it. If left in, at minimum add a placeholder or tooltip indicating "Coming soon" so users know it's unimplemented.


- navigate the sidebar library dropdown by username slug (?? id) so it
  lands on /library/[username] instead of a 404 at /library/[numericId];
  key selectedLibrary lookup on the same slug so live counts resolve
- suffix tag slugs with Date.now() instead of a hardcoded -1 so names
  that normalize alike no longer collide on Strapi's unique-slug constraint
- sanitize object description with sanitizeHtml before dangerouslySetInnerHTML
  to close a stored-XSS vector on user-supplied CKEditor content

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@MaryWylde MaryWylde merged commit d8caeba into dev Jun 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant